#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QString>
#include <QRegularExpression>
#include <QMessageBox>
#include <QThread>
#include <cmath>
#include <QLineSeries>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ad9910 = new AD9910_CTRL(this);

    ui->setupUi(this);

    //* IP config
    ui->widget_IP_address->setIP(QString("10.133.0.34"));

    resetWavePoints();

    //* connect signal and slot
    connect(ad9910->tcpClient, &QTcpSocket::readyRead, this, &MainWindow::readData);

    readDataCache();
}

MainWindow::~MainWindow()
{
    setDataCache();
    ad9910->tcpClient->disconnectFromHost();
    delete ui;
}

void MainWindow::showPoints()
{
    QString pointDispStr;
    for (auto &key : wavePoints.keys())
    {
        pointDispStr.append("(" + QString::number(key) + ", ");
        pointDispStr.append(QString::number(wavePoints.value(key)) + ")\n");
    }
    ui->textBrowser_WaveCfg_PointDisp->setText(pointDispStr);
}

QChart *MainWindow::createWaveChart(const QMap<int, int> &data)
{
    QChart *chart = new QChart();
    chart->setTitle("Waveform Chart");

    //* create series and add data to it
    QLineSeries *series = new QLineSeries(chart);
    for (auto &key : data.keys())
    {
        series->append(QPoint(key, data.value(key)));
    }
    if (!data.contains(0))
    {
        series->append(QPoint(0, 0));
    }
    if (!data.contains(1023))
    {
        series->append(QPoint(1023, 0));
    }

    chart->addSeries(series);

    //* chart format settings
    chart->createDefaultAxes();
    QValueAxis *axisX = qobject_cast<QValueAxis *>(chart->axes(Qt::Horizontal).first());
    QValueAxis *axisY = qobject_cast<QValueAxis *>(chart->axes(Qt::Vertical).first());
    axisX->setRange(0, 1023);
    axisY->setRange(0, 16383);
    axisX->setTitleText("Time");
    axisY->setTitleText("Value");
    axisX->setTitleFont(QFont("Times", 8, QFont::Normal));
    axisY->setTitleFont(QFont("Times", 8, QFont::Normal));
    axisX->setLabelFormat("%d");
    axisY->setLabelFormat("%d");

    chart->legend()->hide();

    return chart;
}

void MainWindow::resetWavePoints()
{
    wavePoints.clear();
    wavePoints.insert(0, 0);
    wavePoints.insert(1023, 0);
}

void MainWindow::readData()
{
    QByteArray data = ad9910->tcpClient->readAll();
}

static constexpr quint16 DEFAULT_PORT = 5000;
void MainWindow::on_pushButton_IP_connect_clicked()
{
    const QString ip = ui->widget_IP_address->getIP();
    ad9910->tcpClient->connectToHost(ip, DEFAULT_PORT);

    if (ad9910->tcpClient->waitForConnected(100000))
    {
        QMessageBox::information(nullptr, "信息", "连接至ARM成功!", QMessageBox::Ok);

        // disable timer if not auto trigger mode
        QByteArray sendByteArray = ad9910->genByteArr_SetTimer(0, 0, 0);
        if (!ad9910->sendSingleData(sendByteArray))
        {
            QMessageBox::warning(nullptr, "警告", "发送SetTimer失败!", QMessageBox::Ok);
        }
    }
    else
    {
        QMessageBox::warning(nullptr, "警告", "连接至ARM失败!", QMessageBox::Ok);
    }
}

void MainWindow::on_pushButton_IP_clear_clicked()
{
    ui->widget_IP_address->clearIP();
}

void MainWindow::on_pushButton_IP_disconnect_clicked()
{
    ad9910->tcpClient->disconnectFromHost();
    timer.stop();
}

void MainWindow::on_pushButton_manualSend_clicked()
{
    QString sendData = ui->lineEdit_manualSend->text();
    sendData.remove(QRegularExpression("\\s+")); // remove all spaces

    const QByteArray &&sendByteArray = QByteArray::fromHex(sendData.toUtf8());
    if (!ad9910->sendSingleData(sendByteArray))
    {
        QMessageBox::warning(nullptr, "警告", "发送数据失败!", QMessageBox::Ok);
    }
}

void MainWindow::on_checkBox_PfCfg_ContReCirc_stateChanged(int arg1)
{
    if (arg1 == Qt::Checked)
    {
        ui->checkBox_PfCfg_AutoTrg->setChecked(false);
        ui->checkBox_PfCfg_ManTrg->setChecked(false);
        ui->spinBox_PfCfg_TrgPeriod->setEnabled(0);
        ui->pushButton_PfCfg_Trg->setEnabled(0);
    }
}

void MainWindow::on_checkBox_PfCfg_ManTrg_stateChanged(int arg1)
{
    if (arg1 == Qt::Checked)
    {
        ui->checkBox_PfCfg_ContReCirc->setChecked(false);
        ui->checkBox_PfCfg_AutoTrg->setChecked(false);
        ui->spinBox_PfCfg_TrgPeriod->setEnabled(0);
        ui->pushButton_PfCfg_Trg->setEnabled(1);
    }
}

void MainWindow::on_checkBox_PfCfg_AutoTrg_stateChanged(int arg1)
{
    if (arg1 == Qt::Checked)
    {
        ui->checkBox_PfCfg_ContReCirc->setChecked(false);
        ui->checkBox_PfCfg_ManTrg->setChecked(false);
        ui->spinBox_PfCfg_TrgPeriod->setEnabled(1);
        ui->pushButton_PfCfg_Trg->setEnabled(0);
    }
}

void MainWindow::on_pushButton_PfCfg_Trg_clicked()
{
    QByteArray sendByteArray = ad9910->genByteArr_Update();
    if (!ad9910->sendSingleData(sendByteArray))
    {
        QMessageBox::warning(nullptr, "警告", "Update失败!", QMessageBox::Ok);
        return;
    }
}

void MainWindow::on_pushButton_PfCfg_Send_clicked()
{
    //* generate and send Profile
    const uint16_t addrStepRate = ui->spinBox_PfCfg_SingleStep->value() / 4;
    RAM_MODE mode = RAM_MODE::ContReCirc;
    if (ui->checkBox_PfCfg_ContReCirc->isChecked())
    {
        mode = RAM_MODE::ContReCirc;
    }
    else if (ui->checkBox_PfCfg_ManTrg->isChecked())
    {
        mode = RAM_MODE::RampUp;
    }
    else if (ui->checkBox_PfCfg_AutoTrg->isChecked())
    {
        mode = RAM_MODE::RampUp;
    }
    QByteArray sendByteArray = ad9910->genByteArr_Profile(addrStepRate, mode);
    if (!ad9910->sendSingleData(sendByteArray))
    {
        QMessageBox::warning(nullptr, "警告", "发送Profile失败!", QMessageBox::Ok);
        return;
    }
    QThread::msleep(20); // 阻塞延时20ms

    //* set Timer if auto trigger mode
    if (ui->checkBox_PfCfg_AutoTrg->isChecked())
    {
        uint32_t sec = 0;
        uint32_t usec = ui->spinBox_PfCfg_TrgPeriod->value();
        if (usec > 1000000)
        {
            sec = usec / 1000000;
            usec = usec % 1000000;
        }
        sendByteArray = ad9910->genByteArr_SetTimer(1, sec, usec);
        if (!ad9910->sendSingleData(sendByteArray))
        {
            QMessageBox::warning(nullptr, "警告", "发送SetTimer失败!", QMessageBox::Ok);
        }
    }
    else
    {
        // disable timer if not auto trigger mode
        sendByteArray = ad9910->genByteArr_SetTimer(0, 0, 0);
        if (!ad9910->sendSingleData(sendByteArray))
        {
            QMessageBox::warning(nullptr, "警告", "发送SetTimer失败!", QMessageBox::Ok);
        }
    }
    QThread::msleep(20); // 阻塞延时20ms
}

void MainWindow::on_pushButton_manualSend_Clear_clicked()
{
    ui->lineEdit_manualSend->clear();
}

void MainWindow::on_spinBox_PfCfg_SingleStep_valueChanged(int arg1)
{
    const double waveLen = arg1 * 1.024;
    ui->lineEdit_PfCg_WaveLen->setText(QString::number(waveLen, 'f', 3));

    if (ui->spinBox_PfCfg_TrgPeriod->value() < waveLen)
    {
        ui->spinBox_PfCfg_TrgPeriod->setValue(ceil(waveLen / 10) * 10);
    }
}

void MainWindow::on_pushButton_WaveCfg_ClearAll_clicked()
{
    resetWavePoints();
    showPoints();
}

void MainWindow::on_pushButton_WaveCfg_AddPoint_clicked()
{
    const int xPos = ui->spinBox_WaveCfg_XPos->value();
    const int yPos = ui->spinBox_WaveCfg_YPos->value();
    wavePoints.insert(xPos, yPos);
    showPoints();
    prevWaveIndex = xPos;

    // generate wave curve
    if (this->waveChartView != Q_NULLPTR)
    {
        ui->gridLayout_waveChart->removeWidget(this->waveChartView);
        delete this->waveChartView;
    }

    this->waveChartView = new QChartView(this->createWaveChart(wavePoints));
    ui->gridLayout_waveChart->addWidget(this->waveChartView);
}

void MainWindow::on_pushButton_WaveCfg_RmLast_clicked()
{
    if (wavePoints.size() > 0)
    {
        wavePoints.remove(wavePoints.lastKey());
        showPoints();
    }
    // generate wave curve
    if (this->waveChartView != Q_NULLPTR)
    {
        ui->gridLayout_waveChart->removeWidget(this->waveChartView);
        delete this->waveChartView;
    }

    this->waveChartView = new QChartView(this->createWaveChart(wavePoints));
    ui->gridLayout_waveChart->addWidget(this->waveChartView);
}

void MainWindow::on_pushButton_WaveCfg_RmPrev_clicked()
{
    if (wavePoints.size() > 0)
    {
        wavePoints.remove(prevWaveIndex);
        showPoints();
    }
    // generate wave curve
    if (this->waveChartView != Q_NULLPTR)
    {
        ui->gridLayout_waveChart->removeWidget(this->waveChartView);
        delete this->waveChartView;
    }

    this->waveChartView = new QChartView(this->createWaveChart(wavePoints));
    ui->gridLayout_waveChart->addWidget(this->waveChartView);
}

void MainWindow::on_pushButton_WaveCfg_SendWaveData_clicked()
{
    QVector<uint16_t> ramData = ad9910->genRamData(wavePoints);
    if (!ad9910->sendRamData(ramData))
    {
        QMessageBox::warning(nullptr, "警告", "发送RAM数据失败!", QMessageBox::Ok);
    }
}

void MainWindow::on_pushButton_WaveCfg_LoadWaveData_clicked()
{
    //* get file path
    const QStringList &folderPath = QFileDialog::getOpenFileNames(this,
                                                                  "选择文件",
                                                                  "./../../Head Wave Parameter/",
                                                                  "Text Files (*.txt);;All Files (*)");
    const QString &filePath = folderPath.isEmpty() ? "" : folderPath[0];

    if (filePath.isEmpty())
    {
        return;
    }
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QMessageBox::warning(nullptr, "Error", "无法以读取模式打开文件: " + file.errorString(), QMessageBox::Ok);
        return;
    }

    //* read file
    QTextStream in(&file);
    QStringList textToRead;
    while (!in.atEnd())
    {
        QString line = in.readLine();
        textToRead.append(line);
    }

    file.close();

    //* parse file
    resetWavePoints();
    for (auto &str : textToRead)
    {
        QStringList strList = str.split(" ");
        if (strList.size() < 2)
        {
            continue;
        }
        const int xPos = strList[0].toInt();
        const int yPos = strList[1].toInt();
        wavePoints.insert(xPos, yPos);
    }

    showPoints();
    // generate wave curve
    if (this->waveChartView != Q_NULLPTR)
    {
        ui->gridLayout_waveChart->removeWidget(this->waveChartView);
        delete this->waveChartView;
    }

    this->waveChartView = new QChartView(this->createWaveChart(wavePoints));
    ui->gridLayout_waveChart->addWidget(this->waveChartView);
}

void MainWindow::on_pushButton_WaveCfg_SaveWaveData_clicked()
{
    QString defaultFileName;
    QString timeStr = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
    defaultFileName = timeStr + ".txt";

    //* save file
    QString fileName = QFileDialog::getSaveFileName(nullptr,
                                                    "保存参数",
                                                    "./../../Head Wave Parameter/" + defaultFileName,
                                                    "Text Files (*.txt);;All Files (*)");
    if (fileName.isEmpty())
    {
        return;
    }

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QMessageBox::warning(nullptr, "Error", "Cannot open file for writing: " + file.errorString(), QMessageBox::Ok);
        return;
    }

    //* get parameter and save to file in QString
    QStringList textToSave;
    for (auto &key : wavePoints.keys())
    {
        textToSave.append(QString::number(key) + " " + QString::number(wavePoints.value(key)));
    }

    QTextStream out(&file);
    for (int i = 0; i < textToSave.size(); i++)
    {
        out << textToSave.at(i) << "\n";
    }
    file.close();

    QMessageBox::information(nullptr, "成功", "波形参数保存成功！", QMessageBox::Ok);
}

/**
 * @brief set the data to cache file
 * Open the specified file and save the data of ui to the file.
 *
 * First, try opening the file named 'DataCache. txt'. If it fails to open, a warning box will pop up indicating that the user cannot open the file.
 * Then retrieve data from the specified UI control and save it to a file.
 * Finally, add a record at the end of the file indicating the number of data items in the file to check its integrity.
 */
void MainWindow::setDataCache()
{
    //* open file
    QString fileName = "DataCache.txt";
    QFile file(fileName);

    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QMessageBox::warning(nullptr, "Error", "Cannot open file DataCache.txt: " + file.errorString(), QMessageBox::Ok);
        return;
    }

    //* get parameter and save to file in QString
    QStringList textToSave;

    textToSave.append(ui->spinBox_PfCfg_SingleStep->text());
    textToSave.append(ui->spinBox_PfCfg_TrgPeriod->text());
    textToSave.append(ui->lineEdit_PfCg_WaveLen->text());

    //* add data size to the end of file to check file integrity
    textToSave.append(QString::number(textToSave.size()));

    QTextStream out(&file);
    for (int i = 0; i < textToSave.size(); i++)
    {
        out << textToSave.at(i) << "\n";
    }
    file.close();
}

/**
 * @brief read data from cache file and set to ui
 * This function reads the data cache from a file named 'DataCache. txt' and sets the read data to the corresponding ui widget.
 * @return null
 */
void MainWindow::readDataCache()
{
    //* open file
    QString fileName = "DataCache.txt";
    QFile file(fileName);

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QMessageBox::warning(nullptr, "Error", "Cannot open file for reading: " + file.errorString(), QMessageBox::Ok);
        return;
    }

    //* read file
    QTextStream in(&file);
    QStringList textToRead;
    while (!in.atEnd())
    {
        QString line = in.readLine();
        textToRead.append(line);
    }
    //* check data size
    if (textToRead.size() != textToRead.last().toInt() + 1)
    {
        QMessageBox::warning(nullptr, "Error", "文件格式错误！", QMessageBox::Ok);
        return;
    }
    file.close();

    //* set parameter
    int i = 0;
    ui->spinBox_PfCfg_SingleStep->setValue(textToRead.at(i++).toUInt());
    ui->spinBox_PfCfg_TrgPeriod->setValue(textToRead.at(i++).toUInt());
    ui->lineEdit_PfCg_WaveLen->setText(textToRead.at(i++));
}
